마라톤 사진 조회 시스템 아키텍처(임시)
업로드 / OCR 파이프라인 (행사 후)
%%{init: {"theme": "dark"}}%%
flowchart TD
A[사진기사 촬영] --> B[행사 종료 후
원본 사진 업로드
20MB × n장]
B --> C[로컬 OCR 배치 스크립트
Google Vision API]
C --> D{배번표 인식}
D -->|성공| E[Supabase
번호 ↔ 사진 URL 매핑 저장]
D -->|실패/미인식| F[수동 처리]
B --> G[Cloudflare R2
원본 이미지 저장]
E --> H[준비 완료]
G --> H참가자 조회 플로우 (행사 당일 / 이후)
%%{init: {"theme": "dark"}}%%
flowchart TD
A[참가자] --> B[번호 입력
웹사이트]
B --> C[Vercel API
번호로 Supabase 조회]
C --> D[매핑된 사진 URL 목록 반환]
D --> E[R2 Presigned URL 생성
시간 제한 있음]
E --> F[참가자에게 URL 전달]
F --> G{원하는 작업}
G -->|사진 다운로드| H[R2에서 직접 다운로드]
G -->|인증서 발급| I[클라이언트에서
Canvas로 조합]
I --> J[JPG / PNG 다운로드]전체 인프라 구성
%%{init: {"theme": "dark"}}%%
flowchart LR
subgraph Local
A[OCR 배치 스크립트]
end
subgraph Cloudflare
B[R2
이미지 저장
egress 무료]
end
subgraph Supabase
C[PostgreSQL
번호 ↔ URL 매핑
Free Tier]
end
subgraph Vercel
D[Next.js
프론트 + API]
end
A -->|사진 업로드| B
A -->|매핑 저장| C
D -->|번호 조회| C
D -->|Presigned URL 생성| B
B -->|이미지 전송| E[참가자]
D --> E비용 추정
| 항목 | 500명 기준 | 3000명 기준 |
|---|---|---|
| R2 저장 (50GB / 300GB) | ~$0.75/월 | ~$4.5/월 |
| R2 Egress | 무료 | 무료 |
| Supabase | 무료 (Free Tier) | 무료 (Free Tier) |
| Vercel | 무료 (Free Tier) | 무료 (Free Tier) |
| Google Vision OCR | ~$3.75 (1회) | ~$20 (1회) |
구성 설명
업로드 / OCR 파이프라인
행사가 끝난 후 사진기사가 촬영한 원본 사진을 업로드한다. 업로드된 사진은 로컬에서 OCR 배치 스크립트를 돌려 배번표 번호를 인식하고, 번호와 사진 URL을 Supabase에 매핑해서 저장한다. OCR 처리는 실시간이 아니라 행사 후 한 번만 돌리면 되기 때문에 서버 타임아웃 걱정 없이 로컬에서 처리하는 게 깔끔하다.
참가자 조회 플로우
참가자가 웹사이트에서 배번표 번호를 입력하면 Vercel API가 Supabase에서 해당 번호에 매핑된 사진 URL을 조회한다. 이때 R2의 원본 URL을 직접 노출하지 않고 Presigned URL을 생성해서 전달한다. Presigned URL은 유효시간이 있어서 번호를 모르는 사람이 URL만으로 접근하는 걸 막을 수 있다. 사진 다운로드 외에 인증서 발급도 지원하며, 인증서는 서버에 저장하지 않고 클라이언트에서 Canvas로 조합해 JPG/PNG로 바로 다운로드하는 방식이라 별도 저장 비용이 없다.
인프라 구성
- Cloudflare R2: 원본 이미지 저장소. egress 비용이 무료라 참가자가 사진을 아무리 다운로드해도 전송 비용이 발생하지 않는다.
- Supabase: 배번표 번호와 사진 URL 매핑 저장용 DB. Free Tier로 충분히 커버 가능하다.
- Vercel: 프론트엔드와 API 서버. OCR을 미리 처리해두기 때문에 조회 시점엔 DB 읽기와 Presigned URL 생성만 하면 돼서 Free Tier로 충분하다.
- 로컬 OCR 스크립트: Google Vision API를 사용해 배번표를 인식한다. 행사마다 1회성으로 돌리는 배치 작업이라 별도 서버 없이 로컬에서 실행한다.